package com.dev;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.content.res.XmlResourceParser;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.provider.OpenableColumns;
import android.text.TextUtils;
import android.webkit.MimeTypeMap;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.xmlpull.v1.XmlPullParserException;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
public class FileProvider extends ContentProvider{
    private static final String[] COLUMNS = {OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE };
    private static final String META_DATA_FILE_PROVIDER_PATHS = "android.support.FILE_PROVIDER_PATHS";
    private static final String TAG_ROOT_PATH = "root-path";
    private static final String TAG_FILES_PATH = "files-path";
    private static final String TAG_CACHE_PATH = "cache-path";
    private static final String TAG_EXTERNAL = "external-path";
    private static final String TAG_EXTERNAL_FILES = "external-files-path";
    private static final String TAG_EXTERNAL_CACHE = "external-cache-path";
    private static final String TAG_EXTERNAL_MEDIA = "external-media-path";
    private static final String ATTR_NAME = "name";
    private static final String ATTR_PATH = "path";
    private static final String DISPLAYNAME_FIELD = "displayName";
    private static final File DEVICE_ROOT = new File("/");
    private static final HashMap<String, PathStrategy> sCache = new HashMap<>();
    @NonNull
    private final Object mLock = new Object();
    private String mAuthority;
    // Do NOT access directly! Use getLocalPathStrategy() instead.
    @Nullable
    private PathStrategy mLocalPathStrategy;
    private final int mResourceId;
    public FileProvider(){
        this(0);
    }
    protected FileProvider(int resourceId){
        mResourceId=resourceId;
    }
    /**
     * The default FileProvider implementation does not need to be initialized. If you want to
     * override this method, you must provide your own subclass of FileProvider.
     */
    @Override
    public boolean onCreate(){
        return true;
    }
    /**
     * After the FileProvider is instantiated, this method is called to provide the system with
     * information about the provider.
     *
     * @param ma A {@link Context} for the current component.
     * @param info A {@link ProviderInfo} for the new provider.
     */
    @SuppressWarnings("StringSplitter")
    @Override
    public void attachInfo(@NonNull Context ma,@NonNull ProviderInfo info){
        super.attachInfo(ma,info);
        // Check our security attributes.
        /*if(info.exported){
            // Our intent here is to help application developers to avoid *accidentally* opening up
            // this provider to the "world" which may lead to vulnerabilities in their applications.
            throw new SecurityException("Provider must not be exported");
        }*/
        if(!info.grantUriPermissions){
            throw new SecurityException("Provider must grant uri permissions");
        }
        final String authority = info.authority.split(";")[0];
        synchronized(mLock){
            mAuthority=authority;
        }
        synchronized(sCache){
            sCache.remove(authority);
        }
    }
    public static Uri getUriForFile(@NonNull Context ma,@NonNull String authority,
	@NonNull File file){
        final PathStrategy strategy = getPathStrategy(ma,authority,0);
        return strategy.getUriForFile(file);
    }
    @SuppressLint("StreamFiles")
    @NonNull
    public static Uri getUriForFile(@NonNull Context ma,@NonNull String authority,
	@NonNull File file,@NonNull String displayName){
        Uri uri = getUriForFile(ma,authority,file);
        return uri.buildUpon().appendQueryParameter(DISPLAYNAME_FIELD,displayName).build();
    }
    @NonNull
    @Override
    public Cursor query(@NonNull Uri uri,@Nullable String[] projection,@Nullable String selection,
	@Nullable String[] selectionArgs,
	@Nullable String sortOrder){
        // ContentProvider has already checked granted permissions
        final File file = getLocalPathStrategy().getFileForUri(uri);
        String displayName = uri.getQueryParameter(DISPLAYNAME_FIELD);
        if(projection==null){
            projection=COLUMNS;
        }
        String[] cols = new String[projection.length];
        Object[] values = new Object[projection.length];
        int i = 0;
        for(String col : projection){
            if(OpenableColumns.DISPLAY_NAME.equals(col)){
                cols[i]=OpenableColumns.DISPLAY_NAME;
                values[i++]=(displayName==null)? file.getName() :displayName;
            }else if(OpenableColumns.SIZE.equals(col)){
                cols[i]=OpenableColumns.SIZE;
                values[i++]=file.length();
            }
        }
        cols=copyOf(cols,i);
        values=copyOf(values,i);
        final MatrixCursor cursor = new MatrixCursor(cols,1);
        cursor.addRow(values);
        return cursor;
    }
    /**
     * Returns the MIME type of a content URI returned by
     * {@link #getUriForFile(Context, String, File) getUriForFile()}.
     *
     * @param uri A content URI returned by
     * {@link #getUriForFile(Context, String, File) getUriForFile()}.
     * @return If the associated file has an extension, the MIME type associated with that
     * extension; otherwise <code>application/octet-stream</code>.
     */
    @Nullable
    @Override
    public String getType(@NonNull Uri uri){
        // ContentProvider has already checked granted permissions
        final File file = getLocalPathStrategy().getFileForUri(uri);
        final int lastDot = file.getName().lastIndexOf('.');
        if(lastDot>=0){
            final String extension = file.getName().substring(lastDot+1);
            final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
            if(mime!=null){
                return mime;
            }
        }
        return "application/octet-stream";
    }
    /**
     * Unrestricted version of getType
     * called, when caller does not have corresponding permissions
     */
    @Nullable
    public String getTypeAnonymous(@NonNull Uri uri){
        return "application/octet-stream";
    }
    /**
     * By default, this method throws an {@link UnsupportedOperationException}. You must
     * subclass FileProvider if you want to provide different functionality.
     */
    @Override
    public Uri insert(@NonNull Uri uri,@NonNull ContentValues values){
        throw new UnsupportedOperationException("No external inserts");
    }
    /**
     * By default, this method throws an {@link UnsupportedOperationException}. You must
     * subclass FileProvider if you want to provide different functionality.
     */
    @Override
    public int update(@NonNull Uri uri,@NonNull ContentValues values,@Nullable String selection,
	@Nullable String[] selectionArgs){
        throw new UnsupportedOperationException("No external updates");
    }
    @Override
    public int delete(@NonNull Uri uri,@Nullable String selection,
	@Nullable String[] selectionArgs){
        // ContentProvider has already checked granted permissions
        final File file = getLocalPathStrategy().getFileForUri(uri);
        return file.delete()? 1 :0;
    }
    @SuppressLint("UnknownNullness") // b/171012356
    @Override
    public ParcelFileDescriptor openFile(@NonNull Uri uri,@NonNull String mode)
	throws FileNotFoundException{
        // ContentProvider has already checked granted permissions
        final File file = getLocalPathStrategy().getFileForUri(uri);
        final int fileMode = modeToMode(mode);
        return ParcelFileDescriptor.open(file,fileMode);
    }
    /** Return the local {@link PathStrategy}, creating it if necessary. */
    @NonNull
    private PathStrategy getLocalPathStrategy(){
        synchronized(mLock){
            /*requireNonNull(mAuthority, "mAuthority is null. Did you override attachInfo and "
			 + "did not call super.attachInfo()?");*/
            if(mLocalPathStrategy==null){
                mLocalPathStrategy=getPathStrategy(getContext(),mAuthority,mResourceId);
            }
            return mLocalPathStrategy;
        }
    }
    /**
     * Return {@link PathStrategy} for given authority, either by parsing or
     * returning from cache.
     */
    private static PathStrategy getPathStrategy(Context ma,String authority,int resourceId){
        PathStrategy strat;
        synchronized(sCache){
            strat=sCache.get(authority);
            if(strat==null){
                try{
                    strat=parsePathStrategy(ma,authority,resourceId);
                }catch(IOException e){
                    throw new IllegalArgumentException(
					"Failed to parse "+META_DATA_FILE_PROVIDER_PATHS+" meta-data",e);
                }catch(XmlPullParserException e){
                    throw new IllegalArgumentException(
					"Failed to parse "+META_DATA_FILE_PROVIDER_PATHS+" meta-data",e);
                }
                sCache.put(authority,strat);
            }
        }
        return strat;
    }
    static XmlResourceParser getFileProviderPathsMetaData(Context ma,String authority,
	@Nullable ProviderInfo info,
	int resourceId){
        if(info==null){
            throw new IllegalArgumentException(
			"Couldn't find meta-data for provider with authority "+authority);
        }
        if(info.metaData==null&&resourceId!=0){
            info.metaData=new Bundle(1);
            info.metaData.putInt(META_DATA_FILE_PROVIDER_PATHS,resourceId);
        }
        final XmlResourceParser in = info.loadXmlMetaData(
		ma.getPackageManager(),META_DATA_FILE_PROVIDER_PATHS);
        if(in==null){
            throw new IllegalArgumentException(
			"Missing "+META_DATA_FILE_PROVIDER_PATHS+" meta-data");
        }
        return in;
    }
    /**
     * Parse and return {@link PathStrategy} for given authority as defined in
     * {@link #META_DATA_FILE_PROVIDER_PATHS} {@code <meta-data>}.
     *
     * @see #getPathStrategy(Context, String, int)
     */
    private static PathStrategy parsePathStrategy(Context ma,String authority,int resourceId)
	throws IOException, XmlPullParserException{
        final SimplePathStrategy strat = new SimplePathStrategy(authority);
        final ProviderInfo info = ma.getPackageManager()
		.resolveContentProvider(authority,PackageManager.GET_META_DATA);
        final XmlResourceParser in = getFileProviderPathsMetaData(ma,authority,info,
		resourceId);
        int type;
        while((type=in.next())!=END_DOCUMENT){
            if(type==START_TAG){
                final String tag = in.getName();
                final String name = in.getAttributeValue(null,ATTR_NAME);
                String path = in.getAttributeValue(null,ATTR_PATH);
                File target = null;
                if(TAG_ROOT_PATH.equals(tag)){
                    target=DEVICE_ROOT;
                }else if(TAG_FILES_PATH.equals(tag)){
                    target=ma.getFilesDir();
                }else if(TAG_CACHE_PATH.equals(tag)){
                    target=ma.getCacheDir();
                }else if(TAG_EXTERNAL.equals(tag)){
                    target=Environment.getExternalStorageDirectory();
                }else if(TAG_EXTERNAL_FILES.equals(tag)){
                    File[] externalFilesDirs = ma.getExternalFilesDirs(null);
                    if(externalFilesDirs.length>0){
                        target=externalFilesDirs[0];
                    }
                }else if(TAG_EXTERNAL_CACHE.equals(tag)){
                    File[] externalCacheDirs = ma.getExternalCacheDirs();
                    if(externalCacheDirs.length>0){
                        target=externalCacheDirs[0];
                    }
                }else if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.LOLLIPOP
				&&TAG_EXTERNAL_MEDIA.equals(tag)){
                    File[] externalMediaDirs = Api21Impl.getExternalMediaDirs(ma);
                    if(externalMediaDirs.length>0){
                        target=externalMediaDirs[0];
                    }
                }
                if(target!=null){
                    strat.addRoot(name,buildPath(target,path));
                }
            }
        }
        return strat;
    }
    /**
     * Strategy for mapping between {@link File} and {@link Uri}.
     * <p>
     * Strategies must be symmetric so that mapping a {@link File} to a
     * {@link Uri} and then back to a {@link File} points at the original
     * target.
     * <p>
     * Strategies must remain consistent across app launches, and not rely on
     * dynamic state. This ensures that any generated {@link Uri} can still be
     * resolved if your process is killed and later restarted.
     *
     * @see SimplePathStrategy
     */
    interface PathStrategy{
        /**
         * Return a {@link Uri} that represents the given {@link File}.
         */
        Uri getUriForFile(File file);
        /**
         * Return a {@link File} that represents the given {@link Uri}.
         */
        File getFileForUri(Uri uri);
    }
    /**
     * Strategy that provides access to files living under a narrow allowed list
     * of filesystem roots. It will throw {@link SecurityException} if callers try
     * accessing files outside the configured roots.
     * <p>
     * For example, if configured with
     * {@code addRoot("myfiles", ma.getFilesDir())}, then
     * {@code ma.getFileStreamPath("foo.txt")} would map to
     * {@code content://myauthority/myfiles/foo.txt}.
     */
    static class SimplePathStrategy implements PathStrategy{
        private final String mAuthority;
        private final HashMap<String, File> mRoots = new HashMap<>();
        SimplePathStrategy(String authority){
            mAuthority=authority;
        }
        /**
         * Add a mapping from a name to a filesystem root. The provider only offers
         * access to files that live under configured roots.
         */
        void addRoot(String name,File root){
            if(TextUtils.isEmpty(name)){
                throw new IllegalArgumentException("Name must not be empty");
            }
            try{
                // Resolve to canonical path to keep path checking fast
                root=root.getCanonicalFile();
            }catch(IOException e){
                throw new IllegalArgumentException(
				"Failed to resolve canonical path for "+root,e);
            }
            mRoots.put(name,root);
        }
        @Override
        public Uri getUriForFile(File file){
            String path;
            try{
                path=file.getCanonicalPath();
            }catch(IOException e){
                throw new IllegalArgumentException("Failed to resolve canonical path for "+file);
            }
            // Find the most-specific root path
            Map.Entry<String, File> mostSpecific = null;
            for(Map.Entry<String, File> root : mRoots.entrySet()){
                final String rootPath = root.getValue().getPath();
                if(belongsToRoot(path,rootPath)&&(mostSpecific==null
				||rootPath.length()>mostSpecific.getValue().getPath().length())){
                    mostSpecific=root;
                }
            }
            if(mostSpecific==null){
                throw new IllegalArgumentException(
				"Failed to find configured root that contains "+path);
            }
            // Start at first char of path under root
            final String rootPath = mostSpecific.getValue().getPath();
            if(rootPath.endsWith("/")){
                path=path.substring(rootPath.length());
            }else{
                path=path.substring(rootPath.length()+1);
            }
            // Encode the tag and path separately
            path=Uri.encode(mostSpecific.getKey())+'/'+Uri.encode(path,"/");
            return new Uri.Builder().scheme("content")
			.authority(mAuthority).encodedPath(path).build();
        }
        @Override
        public File getFileForUri(Uri uri){
            String path = uri.getEncodedPath();
            final int splitIndex = path.indexOf('/',1);
            final String tag = Uri.decode(path.substring(1,splitIndex));
            path=Uri.decode(path.substring(splitIndex+1));
            final File root = mRoots.get(tag);
            if(root==null){
                throw new IllegalArgumentException("Unable to find configured root for "+uri);
            }
            File file = new File(root,path);
            try{
                file=file.getCanonicalFile();
            }catch(IOException e){
                throw new IllegalArgumentException("Failed to resolve canonical path for "+file);
            }
            if(!belongsToRoot(file.getPath(),root.getPath())){
                throw new SecurityException("Resolved path jumped beyond configured root");
            }
            return file;
        }
        /**
         * Check if the given file is located "under" the given root.
         */
        private boolean belongsToRoot(@NonNull String filePath,@NonNull String rootPath){
            // If we naively did the
            //    filePath.startsWith(rootPath)
            // check, we would miss cases such as the following:
            //    rootPath="files/data"
            //    filePath="files/data2"
            // Thus we'll have to do more here.
            // Remove trailing '/'s (if any) first.
            filePath=removeTrailingSlash(filePath);
            rootPath=removeTrailingSlash(rootPath);
            return filePath.equals(rootPath)||filePath.startsWith(rootPath+'/');
        }
    }
    /**
     * Copied from ContentResolver.java
     */
    private static int modeToMode(String mode){
        int modeBits;
        if("r".equals(mode)){
            modeBits=ParcelFileDescriptor.MODE_READ_ONLY;
        }else if("w".equals(mode)||"wt".equals(mode)){
            modeBits=ParcelFileDescriptor.MODE_WRITE_ONLY
			|ParcelFileDescriptor.MODE_CREATE
			|ParcelFileDescriptor.MODE_TRUNCATE;
        }else if("wa".equals(mode)){
            modeBits=ParcelFileDescriptor.MODE_WRITE_ONLY
			|ParcelFileDescriptor.MODE_CREATE
			|ParcelFileDescriptor.MODE_APPEND;
        }else if("rw".equals(mode)){
            modeBits=ParcelFileDescriptor.MODE_READ_WRITE
			|ParcelFileDescriptor.MODE_CREATE;
        }else if("rwt".equals(mode)){
            modeBits=ParcelFileDescriptor.MODE_READ_WRITE
			|ParcelFileDescriptor.MODE_CREATE
			|ParcelFileDescriptor.MODE_TRUNCATE;
        }else{
            throw new IllegalArgumentException("Invalid mode: "+mode);
        }
        return modeBits;
    }
    private static File buildPath(File base,String... segments){
        File cur = base;
        for(String segment : segments){
            if(segment!=null){
                cur=new File(cur,segment);
            }
        }
        return cur;
    }
    private static String[] copyOf(String[] original,int newLength){
        final String[] result = new String[newLength];
        System.arraycopy(original,0,result,0,newLength);
        return result;
    }
    private static Object[] copyOf(Object[] original,int newLength){
        final Object[] result = new Object[newLength];
        System.arraycopy(original,0,result,0,newLength);
        return result;
    }
    @NonNull
    private static String removeTrailingSlash(@NonNull String path){
        if(path.length()>0&&path.charAt(path.length()-1)=='/'){
            return path.substring(0,path.length()-1);
        }else{
            return path;
        }
    }
    static class Api21Impl{
        private Api21Impl(){
            // This class is not instantiable.
        }
        static File[] getExternalMediaDirs(Context ma){
            // Deprecated, otherwise this would belong on ContextCompat as a public method.
            return ma.getExternalMediaDirs();
        }
    }
}
